package de.danoeh.antennapod.net.download.service.episode.autodownload;

import android.content.Context;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Random;

import androidx.test.platform.app.InstrumentationRegistry;

import de.danoeh.antennapod.model.feed.Feed;
import de.danoeh.antennapod.model.feed.FeedCounter;
import de.danoeh.antennapod.model.feed.FeedItem;
import de.danoeh.antennapod.model.feed.FeedItemFilter;
import de.danoeh.antennapod.model.feed.FeedMedia;
import de.danoeh.antennapod.model.feed.FeedOrder;
import de.danoeh.antennapod.model.feed.SortOrder;
import de.danoeh.antennapod.storage.database.DBReader;
import de.danoeh.antennapod.storage.database.DBWriter;
import de.danoeh.antennapod.storage.database.NavDrawerData;
import de.danoeh.antennapod.storage.preferences.UserPreferences;
import de.danoeh.antennapod.storage.database.LongList;
import de.danoeh.antennapod.storage.database.PodDBAdapter;
import org.junit.After;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.experimental.runners.Enclosed;
import org.junit.runner.RunWith;
import org.robolectric.ParameterizedRobolectricTestRunner;
import org.robolectric.RobolectricTestRunner;

import static de.danoeh.antennapod.net.download.service.episode.autodownload.DbTestUtils.saveFeedlist;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;

/**
 * Test class for DBReader.
 */
@SuppressWarnings("ConstantConditions")
@RunWith(Enclosed.class)
public class DbReaderTest {
    @Ignore("Not a test")
    public static class TestBase {
        @Before
        public void setUp() {
            Context context = InstrumentationRegistry.getInstrumentation().getContext();
            UserPreferences.init(context);

            PodDBAdapter.init(context);
            PodDBAdapter.deleteDatabase();
            PodDBAdapter adapter = PodDBAdapter.getInstance();
            adapter.open();
            adapter.close();
        }

        @After
        public void tearDown() {
            PodDBAdapter.tearDownTests();
            DBWriter.tearDownTests();
        }
    }

    @RunWith(RobolectricTestRunner.class)
    public static class SingleTests extends TestBase {
        @Test
        public void testGetFeedList() {
            List<Feed> feeds = saveFeedlist(10, 0, false);
            List<Feed> savedFeeds = DBReader.getFeedList();
            assertNotNull(savedFeeds);
            assertEquals(feeds.size(), savedFeeds.size());
            for (int i = 0; i < feeds.size(); i++) {
                assertEquals(feeds.get(i).getId(), savedFeeds.get(i).getId());
            }
        }

        @Test
        public void testGetFeedListSortOrder() {
            PodDBAdapter adapter = PodDBAdapter.getInstance();
            adapter.open();

            final long lastRefreshed = System.currentTimeMillis();
            Feed feed1 = new Feed(0, null, "A", "link", "d", null, null, null, "rss", "A", null, "", "", lastRefreshed);
            Feed feed2 = new Feed(0, null, "b", "link", "d", null, null, null, "rss", "b", null, "", "", lastRefreshed);
            Feed feed3 = new Feed(0, null, "C", "link", "d", null, null, null, "rss", "C", null, "", "", lastRefreshed);
            Feed feed4 = new Feed(0, null, "d", "link", "d", null, null, null, "rss", "d", null, "", "", lastRefreshed);
            adapter.setCompleteFeed(feed1);
            adapter.setCompleteFeed(feed2);
            adapter.setCompleteFeed(feed3);
            adapter.setCompleteFeed(feed4);
            assertTrue(feed1.getId() != 0);
            assertTrue(feed2.getId() != 0);
            assertTrue(feed3.getId() != 0);
            assertTrue(feed4.getId() != 0);

            adapter.close();

            List<Feed> saved = DBReader.getFeedList();
            assertNotNull(saved);
            assertEquals("Wrong size: ", 4, saved.size());

            assertEquals("Wrong id of feed 1: ", feed1.getId(), saved.get(0).getId());
            assertEquals("Wrong id of feed 2: ", feed2.getId(), saved.get(1).getId());
            assertEquals("Wrong id of feed 3: ", feed3.getId(), saved.get(2).getId());
            assertEquals("Wrong id of feed 4: ", feed4.getId(), saved.get(3).getId());
        }

        @Test
        public void testFeedListDownloadUrls() {
            List<Feed> feeds = saveFeedlist(10, 0, false);
            List<String> urls = DBReader.getFeedListDownloadUrls();
            assertNotNull(urls);
            assertEquals(feeds.size(), urls.size());
            for (int i = 0; i < urls.size(); i++) {
                assertEquals(urls.get(i), feeds.get(i).getDownloadUrl());
            }
        }

        @Test
        public void testLoadFeedDataOfFeedItemlist() {
            final int numFeeds = 10;
            final int numItems = 1;
            List<Feed> feeds = saveFeedlist(numFeeds, numItems, false);
            List<FeedItem> items = new ArrayList<>();
            for (Feed f : feeds) {
                for (FeedItem item : f.getItems()) {
                    item.setFeed(null);
                    item.setFeedId(f.getId());
                    items.add(item);
                }
            }
            DBReader.loadAdditionalFeedItemListData(items);
            for (int i = 0; i < numFeeds; i++) {
                for (int j = 0; j < numItems; j++) {
                    FeedItem item = feeds.get(i).getItems().get(j);
                    assertNotNull(item.getFeed());
                    assertEquals(feeds.get(i).getId(), item.getFeed().getId());
                    assertEquals(item.getFeed().getId(), item.getFeedId());
                }
            }
        }

        @Test
        public void testGetFeedItemList() {
            final int numFeeds = 1;
            final int numItems = 10;
            Feed feed = saveFeedlist(numFeeds, numItems, false).get(0);
            List<FeedItem> items = feed.getItems();
            feed.setItems(null);
            Collections.sort(items, (o1, o2) ->
                    Long.compare(o2.getPubDate().getTime(), o1.getPubDate().getTime()));

            List<FeedItem> savedItems = DBReader.getFeedItemList(feed,
                    FeedItemFilter.unfiltered(), SortOrder.DATE_NEW_OLD, 0, Integer.MAX_VALUE);
            assertNotNull(savedItems);
            assertEquals(items.size(), savedItems.size());
            for (int i = 0; i < savedItems.size(); i++) {
                assertEquals(savedItems.get(i).getId(), items.get(i).getId());
            }
        }

        @SuppressWarnings("SameParameterValue")
        private List<FeedItem> saveQueue(int numItems) {
            if (numItems <= 0) {
                throw new IllegalArgumentException("numItems<=0");
            }
            List<Feed> feeds = saveFeedlist(numItems, numItems, false);
            List<FeedItem> allItems = new ArrayList<>();
            for (Feed f : feeds) {
                allItems.addAll(f.getItems());
            }
            // take random items from every feed
            Random random = new Random();
            List<FeedItem> queue = new ArrayList<>();
            while (queue.size() < numItems) {
                int index = random.nextInt(numItems);
                if (!queue.contains(allItems.get(index))) {
                    queue.add(allItems.get(index));
                }
            }
            PodDBAdapter adapter = PodDBAdapter.getInstance();
            adapter.open();
            adapter.setQueue(queue);
            adapter.close();
            return queue;
        }

        @Test
        public void testGetQueueIdList() {
            final int numItems = 10;
            List<FeedItem> queue = saveQueue(numItems);
            LongList ids = DBReader.getQueueIDList();
            assertNotNull(ids);
            assertEquals(ids.size(), queue.size());
            for (int i = 0; i < queue.size(); i++) {
                assertTrue(ids.get(i) != 0);
                assertEquals(ids.get(i), queue.get(i).getId());
            }
        }

        @Test
        public void testGetQueue() {
            final int numItems = 10;
            List<FeedItem> queue = saveQueue(numItems);
            List<FeedItem> savedQueue = DBReader.getQueue();
            assertNotNull(savedQueue);
            assertEquals(savedQueue.size(), queue.size());
            for (int i = 0; i < queue.size(); i++) {
                assertTrue(savedQueue.get(i).getId() != 0);
                assertEquals(savedQueue.get(i).getId(), queue.get(i).getId());
            }
        }

        @SuppressWarnings("SameParameterValue")
        private List<FeedItem> saveDownloadedItems(int numItems) {
            if (numItems <= 0) {
                throw new IllegalArgumentException("numItems<=0");
            }
            List<Feed> feeds = saveFeedlist(numItems, numItems, true);
            List<FeedItem> items = new ArrayList<>();
            for (Feed f : feeds) {
                items.addAll(f.getItems());
            }
            List<FeedItem> downloaded = new ArrayList<>();
            Random random = new Random();

            while (downloaded.size() < numItems) {
                int i = random.nextInt(numItems);
                if (!downloaded.contains(items.get(i))) {
                    FeedItem item = items.get(i);
                    item.getMedia().setDownloaded(true, System.currentTimeMillis());
                    item.getMedia().setLocalFileUrl("file" + i);
                    downloaded.add(item);
                }
            }
            PodDBAdapter adapter = PodDBAdapter.getInstance();
            adapter.open();
            adapter.storeFeedItemlist(downloaded);
            adapter.close();
            return downloaded;
        }

        @Test
        public void testGetDownloadedItems() {
            final int numItems = 10;
            List<FeedItem> downloaded = saveDownloadedItems(numItems);
            List<FeedItem> downloadedSaved = DBReader.getEpisodes(0, Integer.MAX_VALUE,
                    new FeedItemFilter(FeedItemFilter.DOWNLOADED), SortOrder.DATE_NEW_OLD);
            assertNotNull(downloadedSaved);
            assertEquals(downloaded.size(), downloadedSaved.size());
            for (FeedItem item : downloadedSaved) {
                assertNotNull(item.getMedia());
                assertTrue(item.getMedia().isDownloaded());
                assertNotNull(item.getMedia().getDownloadUrl());
            }
        }

        @SuppressWarnings("SameParameterValue")
        private List<FeedItem> saveNewItems(int numItems) {
            List<Feed> feeds = saveFeedlist(numItems, numItems, true);
            List<FeedItem> items = new ArrayList<>();
            for (Feed f : feeds) {
                items.addAll(f.getItems());
            }
            List<FeedItem> newItems = new ArrayList<>();
            Random random = new Random();

            while (newItems.size() < numItems) {
                int i = random.nextInt(numItems);
                if (!newItems.contains(items.get(i))) {
                    FeedItem item = items.get(i);
                    item.setNew();
                    newItems.add(item);
                }
            }
            PodDBAdapter adapter = PodDBAdapter.getInstance();
            adapter.open();
            adapter.storeFeedItemlist(newItems);
            adapter.close();
            return newItems;
        }

        @Test
        public void testGetNewItemIds() {
            final int numItems = 10;

            List<FeedItem> newItems = saveNewItems(numItems);
            long[] unreadIds = new long[newItems.size()];
            for (int i = 0; i < newItems.size(); i++) {
                unreadIds[i] = newItems.get(i).getId();
            }
            List<FeedItem> newItemsSaved = DBReader.getEpisodes(0, Integer.MAX_VALUE,
                    new FeedItemFilter(FeedItemFilter.NEW), SortOrder.DATE_NEW_OLD);
            assertNotNull(newItemsSaved);
            assertEquals(newItemsSaved.size(), newItems.size());
            for (FeedItem feedItem : newItemsSaved) {
                long savedId = feedItem.getId();
                boolean found = false;
                for (long id : unreadIds) {
                    if (id == savedId) {
                        found = true;
                        break;
                    }
                }
                assertTrue(found);
            }
        }

        @Test
        public void testGetPlaybackHistoryLength() {
            final int totalItems = 100;

            Feed feed = DbTestUtils.saveFeedlist(1, totalItems, true).get(0);

            PodDBAdapter adapter = PodDBAdapter.getInstance();
            for (int playedItems : Arrays.asList(0, 1, 20, 100)) {
                adapter.open();
                for (int i = 0; i < playedItems; ++i) {
                    FeedMedia m = feed.getItems().get(i).getMedia();
                    m.setLastPlayedTimeHistory(new Date(i + 1));

                    adapter.setFeedMediaLastPlayedTimeHistory(m);
                }
                adapter.close();

                long len = DBReader.getTotalEpisodeCount(new FeedItemFilter(FeedItemFilter.IS_IN_HISTORY));
                assertEquals("Wrong size: ", (int) len, playedItems);
            }

        }

        @Test
        public void testGetNavDrawerDataQueueEmptyNoUnreadItems() {
            final int numFeeds = 10;
            final int numItems = 10;
            DbTestUtils.saveFeedlist(numFeeds, numItems, true);
            NavDrawerData navDrawerData = DBReader.getNavDrawerData(
                    UserPreferences.getSubscriptionsFilter(), FeedOrder.COUNTER, FeedCounter.SHOW_NEW,
                    Feed.STATE_SUBSCRIBED);
            assertEquals(numFeeds, navDrawerData.feeds.size());
            assertEquals(0, navDrawerData.numNewItems);
            assertEquals(0, navDrawerData.queueSize);
        }

        @Test
        public void testGetNavDrawerDataQueueNotEmptyWithUnreadItems() {
            final int numFeeds = 10;
            final int numItems = 10;
            final int numQueue = 1;
            final int numNew = 2;
            List<Feed> feeds = DbTestUtils.saveFeedlist(numFeeds, numItems, true);
            PodDBAdapter adapter = PodDBAdapter.getInstance();
            adapter.open();
            for (int i = 0; i < numNew; i++) {
                FeedItem item = feeds.get(0).getItems().get(i);
                item.setNew();
                adapter.setSingleFeedItem(item);
            }
            List<FeedItem> queue = new ArrayList<>();
            for (int i = 0; i < numQueue; i++) {
                FeedItem item = feeds.get(1).getItems().get(i);
                queue.add(item);
            }
            adapter.setQueue(queue);

            adapter.close();

            NavDrawerData navDrawerData = DBReader.getNavDrawerData(
                    UserPreferences.getSubscriptionsFilter(), FeedOrder.COUNTER, FeedCounter.SHOW_NEW,
                    Feed.STATE_SUBSCRIBED);
            assertEquals(numFeeds, navDrawerData.feeds.size());
            assertEquals(numNew, navDrawerData.numNewItems);
            assertEquals(numQueue, navDrawerData.queueSize);
        }

        @Test
        public void testGetFeedItemlistCheckChaptersFalse() {
            List<Feed> feeds = DbTestUtils.saveFeedlist(10, 10, false, false, 0);
            for (Feed feed : feeds) {
                for (FeedItem item : feed.getItems()) {
                    assertFalse(item.hasChapters());
                }
            }
        }

        @Test
        public void testGetFeedItemlistCheckChaptersTrue() {
            List<Feed> feeds = saveFeedlist(10, 10, false, true, 10);
            for (Feed feed : feeds) {
                for (FeedItem item : feed.getItems()) {
                    assertTrue(item.hasChapters());
                }
            }
        }

        @Test
        public void testLoadChaptersOfFeedItemNoChapters() {
            List<Feed> feeds = saveFeedlist(1, 3, false, false, 0);
            saveFeedlist(1, 3, false, true, 3);
            for (Feed feed : feeds) {
                for (FeedItem item : feed.getItems()) {
                    assertFalse(item.hasChapters());
                    item.setChapters(DBReader.loadChaptersOfFeedItem(item));
                    assertFalse(item.hasChapters());
                    assertNull(item.getChapters());
                }
            }
        }

        @Test
        public void testLoadChaptersOfFeedItemWithChapters() {
            final int numChapters = 3;
            DbTestUtils.saveFeedlist(1, 3, false, false, 0);
            List<Feed> feeds = saveFeedlist(1, 3, false, true, numChapters);
            for (Feed feed : feeds) {
                for (FeedItem item : feed.getItems()) {
                    assertTrue(item.hasChapters());
                    item.setChapters(DBReader.loadChaptersOfFeedItem(item));
                    assertTrue(item.hasChapters());
                    assertNotNull(item.getChapters());
                    assertEquals(numChapters, item.getChapters().size());
                }
            }
        }

        @Test
        public void testGetItemWithChapters() {
            final int numChapters = 3;
            List<Feed> feeds = saveFeedlist(1, 1, false, true, numChapters);
            FeedItem item1 = feeds.get(0).getItems().get(0);
            FeedItem item2 = DBReader.getFeedItem(item1.getId());
            item2.setChapters(DBReader.loadChaptersOfFeedItem(item2));
            assertTrue(item2.hasChapters());
            assertEquals(item1.getChapters().size(), item2.getChapters().size());
            for (int i = 0; i < item1.getChapters().size(); i++) {
                assertEquals(item1.getChapters().get(i).getId(), item2.getChapters().get(i).getId());
            }
        }

        @Test
        public void testGetItemByEpisodeUrl() {
            List<Feed> feeds = saveFeedlist(1, 1, true);
            FeedItem item1 = feeds.get(0).getItems().get(0);
            FeedItem feedItemByEpisodeUrl = DBReader.getFeedItemByGuidOrEpisodeUrl(null,
                    item1.getMedia().getDownloadUrl());
            assertEquals(item1.getItemIdentifier(), feedItemByEpisodeUrl.getItemIdentifier());
        }

        @Test
        public void testGetItemByGuid() {
            List<Feed> feeds = saveFeedlist(1, 1, true);
            FeedItem item1 = feeds.get(0).getItems().get(0);

            FeedItem feedItemByGuid = DBReader.getFeedItemByGuidOrEpisodeUrl(item1.getItemIdentifier(),
                    item1.getMedia().getDownloadUrl());
            assertEquals(item1.getItemIdentifier(), feedItemByGuid.getItemIdentifier());
        }

    }

    @RunWith(ParameterizedRobolectricTestRunner.class)
    public static class PlaybackHistoryTest extends TestBase {

        private int paramOffset;
        private int paramLimit;

        @ParameterizedRobolectricTestRunner.Parameters(name = "offset={0} limit={1}")
        public static Collection<Object[]> data() {
            List<Integer> limits = Arrays.asList(1, 20, 100);
            List<Integer> offsets = Arrays.asList(0, 10, 20);
            Object[][] rv = new Object[limits.size() * offsets.size()][2];
            int i = 0;
            for (int offset : offsets) {
                for (int limit : limits) {
                    rv[i][0] = offset;
                    rv[i][1] = limit;
                    i++;
                }
            }

            return Arrays.asList(rv);
        }

        public PlaybackHistoryTest(int offset, int limit) {
            this.paramOffset = offset;
            this.paramLimit = limit;

        }

        @Test
        public void testGetPlaybackHistory() {
            final int numItems = (paramLimit + 1) * 2;
            final int playedItems = paramLimit + 1;
            final int numReturnedItems = Math.min(Math.max(playedItems - paramOffset, 0), paramLimit);
            final int numFeeds = 1;

            Feed feed = DbTestUtils.saveFeedlist(numFeeds, numItems, true).get(0);
            long[] ids = new long[playedItems];

            PodDBAdapter adapter = PodDBAdapter.getInstance();
            adapter.open();
            for (int i = 0; i < playedItems; i++) {
                FeedMedia m = feed.getItems().get(i).getMedia();
                m.setLastPlayedTimeHistory(new Date(i + 1));
                adapter.setFeedMediaLastPlayedTimeHistory(m);
                ids[ids.length - 1 - i] = m.getItem().getId();
            }
            adapter.close();

            List<FeedItem> saved = DBReader.getEpisodes(paramOffset, paramLimit,
                    new FeedItemFilter(FeedItemFilter.IS_IN_HISTORY), SortOrder.COMPLETION_DATE_NEW_OLD);
            assertNotNull(saved);
            assertEquals(String.format("Wrong size with offset %d and limit %d: ",
                            paramOffset, paramLimit),
                    numReturnedItems, saved.size());
            for (int i = 0; i < numReturnedItems; i++) {
                FeedItem item = saved.get(i);
                assertNotNull(item.getMedia().getLastPlayedTimeHistory());
                assertEquals(String.format("Wrong sort order with offset %d and limit %d: ",
                                paramOffset, paramLimit),
                        item.getId(), ids[paramOffset + i]);
            }
        }
    }
}
